import { Cartesian4 } from '../../Source/Cesium.js';
import { ComponentDatatype } from '../../Source/Cesium.js';
import { Math as CesiumMath } from '../../Source/Cesium.js';
import { PixelDatatype } from '../../Source/Cesium.js';
import { Texture } from '../../Source/Cesium.js';
import { BatchTable } from '../../Source/Cesium.js';
import createScene from '../createScene.js';

describe('Scene/BatchTable', function() {

    var unsignedByteAttributes = [{
        functionName : 'batchTable_getShow',
        componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
        componentsPerAttribute : 1
    }, {
        functionName : 'batchTable_getPickColor',
        componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
        componentsPerAttribute : 4,
        normalize : true
    }];

    var floatAttributes = [{
        functionName : 'batchTable_getShow',
        componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
        componentsPerAttribute : 1
    }, {
        functionName : 'batchTable_getCenter',
        componentDatatype : ComponentDatatype.FLOAT,
        componentsPerAttribute : 4
    }];

    var batchTable;
    var scene;
    var context;

    beforeAll(function() {
        scene = createScene();
        context = scene.context;
    });

    afterAll(function() {
        scene.destroyForSpecs();
    });

    afterEach(function() {
        batchTable = batchTable && !batchTable.isDestroyed() && batchTable.destroy();
    });

    it('constructor', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 2);
        expect(batchTable.attributes).toBe(unsignedByteAttributes);
        expect(batchTable.numberOfInstances).toEqual(2);
    });

    it('constructior throws without context', function() {
        expect(function() {
            batchTable = new BatchTable(undefined, unsignedByteAttributes, 5);
        }).toThrowDeveloperError();
    });

    it('constructior throws without attributes', function() {
        expect(function() {
            batchTable = new BatchTable(context, undefined, 5);
        }).toThrowDeveloperError();
    });

    it('constructor throws without number of instances', function() {
        expect(function() {
            batchTable = new BatchTable(context, unsignedByteAttributes, undefined);
        }).toThrowDeveloperError();
    });

    it('sets and gets entries in the table', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);

        var i;
        var color = new Cartesian4(0, 1, 2, 3);

        for (i = 0; i < batchTable.numberOfInstances; ++i) {
            batchTable.setBatchedAttribute(i, 0, 1);
            batchTable.setBatchedAttribute(i, 1, color);
        }

        for (i = 0; i < batchTable.numberOfInstances; ++i) {
            expect(batchTable.getBatchedAttribute(3, 0)).toEqual(1);
            expect(batchTable.getBatchedAttribute(3, 1)).toEqual(color);
        }

        color = new Cartesian4(4, 5, 6, 7);
        batchTable.setBatchedAttribute(3, 0, 0);
        batchTable.setBatchedAttribute(3, 1, color);
        expect(batchTable.getBatchedAttribute(3, 0)).toEqual(0);
        expect(batchTable.getBatchedAttribute(3, 1)).toEqual(color);
    });

    it('sets and gets entries in the table with float attributes', function() {
        var context = {
            floatingPointTexture : true
        };
        batchTable = new BatchTable(context, floatAttributes, 5);

        var i;
        var color = new Cartesian4(0, 1, 2, 3);

        for (i = 0; i < batchTable.numberOfInstances; ++i) {
            batchTable.setBatchedAttribute(i, 0, 1);
            batchTable.setBatchedAttribute(i, 1, color);
        }

        for (i = 0; i < batchTable.numberOfInstances; ++i) {
            expect(batchTable.getBatchedAttribute(3, 0)).toEqual(1);
            expect(batchTable.getBatchedAttribute(3, 1)).toEqual(color);
        }

        color = new Cartesian4(4, 5, 6, 7);
        batchTable.setBatchedAttribute(3, 0, 0);
        batchTable.setBatchedAttribute(3, 1, color);
        expect(batchTable.getBatchedAttribute(3, 0)).toEqual(0);
        expect(batchTable.getBatchedAttribute(3, 1)).toEqual(color);
    });

    it('sets and gets entries in the table with float attributes and forced packing', function() {
        var context = {
            floatingPointTexture : false
        };
        batchTable = new BatchTable(context, floatAttributes, 5);

        var i;
        var color = new Cartesian4(1.23456e12, -2.34567e30, 3.45678e-6, -4.56789e-10);

        for (i = 0; i < batchTable.numberOfInstances; ++i) {
            batchTable.setBatchedAttribute(i, 0, 1);
            batchTable.setBatchedAttribute(i, 1, color);
        }

        var value;
        for (i = 0; i < batchTable.numberOfInstances; ++i) {
            value = batchTable.getBatchedAttribute(3, 0);
            expect(value).toEqual(1);
            value = batchTable.getBatchedAttribute(3, 1);
            expect(value).toEqualEpsilon(color, CesiumMath.EPSILON6);
        }

        color = new Cartesian4(0, Number.MAX_VALUE, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY);
        batchTable.setBatchedAttribute(3, 0, 0);
        batchTable.setBatchedAttribute(3, 1, color);

        value = batchTable.getBatchedAttribute(3, 0);
        expect(value).toEqual(0);
        value = batchTable.getBatchedAttribute(3, 1);
        expect(value.x).toEqual(0.0);
        expect(value.y).toEqual(Number.POSITIVE_INFINITY);
        expect(value.z).toEqual(Number.POSITIVE_INFINITY);
        expect(value.w).toEqual(Number.NEGATIVE_INFINITY);
    });

    it('gets with result parameter', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        var color = new Cartesian4(0, 1, 2, 3);
        batchTable.setBatchedAttribute(0, 1, color);

        var result = new Cartesian4();
        var returndValue = batchTable.getBatchedAttribute(0, 1, result);
        expect(returndValue).toBe(result);
        expect(result).toEqual(color);
    });

    it('get entry throws when instance index is out of range', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        expect(function() {
            batchTable.getBatchedAttribute(-1, 0);
        }).toThrowDeveloperError();
        expect(function() {
            batchTable.getBatchedAttribute(100, 0);
        }).toThrowDeveloperError();
    });

    it('get entry throws when attribute index is out of range', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        expect(function() {
            batchTable.getBatchedAttribute(0, -1);
        }).toThrowDeveloperError();
        expect(function() {
            batchTable.getBatchedAttribute(0, 100);
        }).toThrowDeveloperError();
    });

    it('set entry throws when instance index is out of range', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        expect(function() {
            batchTable.setBatchedAttribute(-1, 0, 0);
        }).toThrowDeveloperError();
        expect(function() {
            batchTable.setBatchedAttribute(100, 0, 1);
        }).toThrowDeveloperError();
    });

    it('set entry throws when attribute index is out of range', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        expect(function() {
            batchTable.setBatchedAttribute(0, -1, 1);
        }).toThrowDeveloperError();
        expect(function() {
            batchTable.setBatchedAttribute(0, 100, 1);
        }).toThrowDeveloperError();
    });

    it('set entry throws when value is undefined', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        expect(function() {
            batchTable.setBatchedAttribute(0, 0, undefined);
        }).toThrowDeveloperError();
    });

    it('creates a uniform callback with unsigned byte texture', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        batchTable.update(scene.frameState);

        var uniforms = batchTable.getUniformMapCallback()({});
        expect(uniforms.batchTexture).toBeDefined();
        expect(uniforms.batchTexture()).toBeInstanceOf(Texture);
        expect(uniforms.batchTexture().pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        expect(uniforms.batchTextureDimensions).toBeDefined();
        expect(uniforms.batchTextureDimensions().x).toBeGreaterThan(0);
        expect(uniforms.batchTextureDimensions().y).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep).toBeDefined();
        expect(uniforms.batchTextureStep().x).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep().y).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep().z).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep().w).toBeGreaterThan(0);
    });

    it('creates a uniform callback with float texture', function() {
        if (!context.floatingPointTexture) {
            return;
        }

        batchTable = new BatchTable(context, floatAttributes, 5);
        batchTable.update(scene.frameState);

        var uniforms = batchTable.getUniformMapCallback()({});
        expect(uniforms.batchTexture).toBeDefined();
        expect(uniforms.batchTexture()).toBeInstanceOf(Texture);
        expect(uniforms.batchTexture().pixelDatatype).toEqual(PixelDatatype.FLOAT);
        expect(uniforms.batchTextureDimensions).toBeDefined();
        expect(uniforms.batchTextureDimensions().x).toBeGreaterThan(0);
        expect(uniforms.batchTextureDimensions().y).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep).toBeDefined();
        expect(uniforms.batchTextureStep().x).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep().y).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep().z).toBeGreaterThan(0);
        expect(uniforms.batchTextureStep().w).toBeGreaterThan(0);

        if (scene.context.floatingPointTexture) {
            expect(uniforms.batchTexture().pixelDatatype).toEqual(PixelDatatype.FLOAT);
        } else {
            expect(uniforms.batchTexture().pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        }
    });

    it('create shader functions', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);

        var shader = 'void main() { gl_Position = vec4(0.0); }';
        var modifiedShader = batchTable.getVertexShaderCallback()(shader);
        expect(modifiedShader.indexOf(batchTable.attributes[0].functionName)).not.toEqual(-1);
        expect(modifiedShader.indexOf(batchTable.attributes[1].functionName)).not.toEqual(-1);
    });

    it('isDestroyed', function() {
        batchTable = new BatchTable(context, unsignedByteAttributes, 5);
        expect(batchTable.isDestroyed()).toEqual(false);
        batchTable.destroy();
        expect(batchTable.isDestroyed()).toEqual(true);
    });
}, 'WebGL');
